import requests
import time
import os

def download_progressbar_display(url,path=None,chunk_size=102400,length=20,print_set=1234,progressbar_set=12345,max_retry=5,continue_download=False):
    """下载文件(download_progressbar_display),包括进度条显示等,参数如下
    必选参数:   
                url:要下载的网址
    可选参数:   
                path:保存下载文件的路径包括文件名和后缀(默认在当前文件夹下创建)
                chunk_size:每次下载的大小(102400(单位B))
                length:进度条长度(20(单位格))
                print_set:打印设置(1234(0:不打印,1:打印文件信息,2:打印进度条,3:打印下载记录,4:时间戳))
                progressbar_set:打印进度条设置(12345(1:进度条,2:文件大小,3:速度,4:进度,5:重试次数(顺序可以调换)))
                max_retry:最大重试次数(5(单位次))
                continue_download:断点下载,程序意外关闭继续下载(False(布尔值))"""
    if '4' in str(print_set):
        print(str(time.strftime("开始时间:%Y-%m-%d %H:%M:%S|星期:%A|时差:%z|时间戳:"))+'%.24f'%time.time()+'|')
    def add_unit(data_size):
        """给数据添加合适的单位(数据大小单位:B)"""
        storage_unit = ['B','KB','MB','GB','TB','PB','EB','ZB','YB','BB','NB','DB','CB','XB']
        for i in range(len(storage_unit)):
            if data_size < 1024:
                return str('%.2f'%data_size)+storage_unit[i]
            else:
                data_size = data_size / 1024
        data_size = data_size * 1024
        return str('%.2f'%data_size)+storage_unit[i]
    def download_display(url=url,path=path,chunk_size=chunk_size,length=length,print_set=print_set,progressbar_set=progressbar_set,write_pattern='wb',start_size=0,retry_num=0):
        """下载文件参数上同,额外参数:
            write_pattern:写入模式
            start_size:开始下载的文件位置
            retry_num:重试数"""
        header = {"Range":f"bytes={start_size}-"}#设置从什么地方开始下载
        #stream参数设置成True时,只下载请求头,要用iter_content或iter_lines访问
        response = requests.get(url,headers=header,stream=True)
        content_size_b = int(response.headers['content-length'])+start_size#返回的response的headers中获取文件大小信息
        content_size = add_unit(content_size_b)
        file_name = response.url.split('/')[-1].split('?')[0]
        if path is None:
            path = os.getcwd()+'/'+file_name
        if '1' in str(print_set):
            print('文件大小:%sB = %s | 文件名字:%s / %s'%(content_size_b,content_size,path.split('/')[-1].split('\\')[-1],file_name))
        if int(response.headers['content-length']) == 0:
            return 2906 #文件没有数据大小为0 ; 2906为'File does not have data size of 0'ascii码总和
        try:
            size = 0 ; start = time.time() ; length = length + 1
            download_size = 0 ; download_time = time.time() ; download_speed = '0.00B/s'
            with open(path,write_pattern) as f:
                for data in response.iter_content(chunk_size=chunk_size):#每次只获取一个chunk_size大小
                    f.write(data)#每次只写入data大小
                    size = len(data)+size
                    download_progress = (size+start_size)/content_size_b
                    if '2' in str(print_set):
                        if int(download_progress) == 1:
                            length -= 1
                            if download_speed == '0.00B/s':
                                download_speed = content_size + '/s'
                        if '3' in str(progressbar_set):
                            if time.time() - download_time >= 1:#下载速度每秒更新,太快会出现ZeroDivisionError,性能也吃不消
                                download_speed = add_unit((size - download_size)/(time.time() - download_time))+'/s'
                                download_time = time.time()
                                download_size = size
                        #'\r'每次重新从开始输出,end = ''是不换行
                        progress_l = [] ; progress_pt = ''
                        progress_l.append('下载进度:|'+int(download_progress*length)*'▌'+int(length-(download_progress*length))*' '+'|')
                        progress_l.append('%+9.9s'%add_unit(size+start_size)+'/%-9.9s'%content_size+'|')
                        progress_l.append('速度:%+11.11s'%download_speed+'|')
                        progress_l.append('进度:%+6.6s'%str('%.2f'%(float(download_progress)*100))+'%|')
                        progress_l.append('重试:%+2.2s'%str(retry_num)+'次|')
                        for i in str(progressbar_set):
                            try:
                                progress_pt = progress_pt + progress_l[int(i)-1]
                            except:
                                    progress_pt = progress_pt + progress_l[0]
                        print('\r'+progress_pt,end='')
                print()
                f.close()
        except Exception as error:
            return  repr(error).split('(')[0] , error , size , content_size_b
        if '3' in str(print_set):
            try:#排除速度太快导致的时间太短引起的ZeroDivisionError(除零错误)
                print('下载耗时:'+str('%.2f'%(time.time()-start))+'秒|平均下载速度:'+add_unit(content_size_b/(time.time()-start))+'/s|')
            except:
                print('下载耗时:'+str('%.2f'%(time.time()-start))+'秒|平均下载速度:'+download_speed+'|')
        return 0
    return_number = None
    if continue_download:#是否文件断点续传
        if os.path.exists(path):#文件是否存在
            if os.path.isfile(path):#文件是否为文件,不为文件夹
                if os.access(path,os.W_OK):#文件是否可读
                    return_number = download_display(write_pattern='ab',start_size=os.path.getsize(path))
    if return_number is None:
        return_number = download_display()
    if isinstance(return_number,tuple):
        start = time.time() ; start_size = 0
        content_size_b = return_number[-1]
        for i in range(1,max_retry+1):
            if isinstance(return_number,tuple):
                start_size += return_number[-2]
                if '2' in str(print_set):
                    return_number = download_display(print_set=2,write_pattern='ab',start_size=start_size,retry_num=i)
                else:
                    return_number = download_display(print_set=0,write_pattern='ab',start_size=start_size,retry_num=i)
            if isinstance(return_number,int):
                if '3' in str(print_set):
                    try:#排除速度太快导致的时间太短引起的ZeroDivisionError(除零错误)
                        print('下载耗时:'+str('%.2f'%(time.time()-start))+'秒|平均下载速度:'+add_unit(content_size_b/(time.time()-start))+'/s|重试次数:'+str(i)+'次|')
                    except:
                        print('下载耗时:'+str('%.2f'%(time.time()-start))+'秒|平均下载速度:'+add_unit(content_size_b)+'/s|重试次数:'+str(i)+'次|')
                break
        if isinstance(return_number,tuple):
            print('\n下载失败,重试次数:'+str(i)+'次')
            return 3485 #下载失败重试到达最大值 ; 3485为'Download Failed Retry Reached Maximum'ascii码总和
    if return_number == 2906:
        if continue_download:
            print('文件大小完整无需下载,如果有问题请删除重下')
        else:
            print('下载链接的文件没有数据,大小为0')
    if '4' in str(print_set):
        print(str(time.strftime("结束时间:%Y-%m-%d %H:%M:%S|星期:%A|时差:%z|时间戳:"))+'%.24f'%time.time()+'|')

#效果,调用示例
download_progressbar_display('https://dldir1.qq.com/qqtv/TencentVideo11.65.4506.0.exe','./腾讯视频.exe',continue_download=False)